#define PrynGuiInternal
#define PrynGuiImplementation
#include <prynGui.h>

PrynGuiExport (PrynResult, prynGuiEventState, (PrynGuiEvent *event, PrynState **output))
{
	if (output) *output = 0;
	if (!event || !event->pControl) return PrynResultNullArgument;
	if (output) *output = event->pControl->pState;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventType, (PrynGuiEvent *event, PrynGuiEventType *output))
{
	if (output) *output = PrynGuiEventTypeNone;
	if (!event) return PrynResultNullArgument;
	if (output) *output = event->pType;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventControl, (PrynGuiEvent *event, PrynGuiControl **output))
{
	if (output) *output = 0;
	if (!event) return PrynResultNullArgument;
	if (output) *output = event->pControl;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventPainter, (PrynGuiEvent *event, PrynGuiPainter **output))
{
	if (output) *output = 0;
	if (!event) return PrynResultNullArgument;
	if (event->pType != PrynGuiEventTypePaint) return PrynResultBadArgument;
	if (output) *output = event->pPainter;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventTypeMatch, (PrynGuiEventType type, PrynGuiEventType match))
{
	// Iteratively test type against match, making type more and more abstract. We're done if type is equal to match, or if type won't become more abstract.
	while (1)
	{
		PrynGuiEventType was;

		if (type == match) return PrynResultTrue;
		was = type;
		prynGuiEventTypeAbstract (type, &type);
		if (type == was) return PrynResultFalse;
	}
}

PrynGuiExport (PrynResult, prynGuiEventTypeAbstract, (PrynGuiEventType type, PrynGuiEventType *output))
{
	#define case(NAME, RESULT) case PrynGuiEventType##NAME: if (output) *output = PrynGuiEventType##RESULT; return PrynResultSuccess
	if (output) *output = PrynGuiEventTypeNone;
	switch (type)
	{
		case (None, None);
		case (All, All);
		case (Paint, All);
		case (Mouse, All);
		case (MouseMove, Mouse);
		case (MouseButton, Mouse);
		case (MouseLeftButton, MouseButton);
		case (MouseLeftButtonDown, MouseLeftButton);
		case (MouseLeftButtonUp, MouseLeftButton);
		case (MouseMiddleButton, MouseButton);
		case (MouseMiddleButtonDown, MouseMiddleButton);
		case (MouseMiddleButtonUp, MouseMiddleButton);
		case (MouseRightButton, MouseButton);
		case (MouseRightButtonDown, MouseRightButton);
		case (MouseRightButtonUp, MouseRightButton);
		case (Destroy, All);
	}
	#undef case
	return PrynResultBadArgument;
}

PrynGuiExport (PrynResult, prynGuiControlRegisterReceiver, (PrynGuiControl *control, PrynGuiEventType type, void *data, PrynGuiReceiverFunction function, PrynGuiReceiverDestroyFunction destroy))
{
	PrynStartFunction ();
	PrynGuiReceiver *receiver;

	if (control) state = control->pState;
	prynAssert (control && function, NullArgument);
	
	prynCall (prynAllocateClearedObject (control->pState, &receiver));
		
	receiver->pType = type;
	receiver->pData = data;
	receiver->pFunction = function;
	receiver->pDestroyFunction = destroy;
	prynDListAppendP (control->pReceivers, receiver);

	PrynEndFunction ();
}

PrynGuiExport (PrynResult, prynGuiEventDispatch, (PrynGuiEvent *event))
{
	PrynStartFunction ();
	PrynGuiReceiver *receiver;

	prynAssert (event && event->pControl, NullArgument);
	state = event->pControl->pState;
	
	// Ensure that the event has the proper parameters.
	switch (event->pType)
	{
		case PrynGuiEventTypePaint: prynAssert (event->pPainter, BadArgument);
		default: break;
	}
	
	prynDListEachP (receiver, event->pControl->pReceivers)
	{
		if (PrynIsTrue (prynGuiEventTypeMatch (event->pType, receiver->pType)))
		{
			PrynResult result = receiver->pFunction (event, receiver->pData);
			event->pReceived = true;
			prynCall (result);
		}
	}
	
	PrynEndFunction ();
}

PrynGuiExport (PrynResult, prynGuiEventEmpty, (PrynGuiEvent *event))
{
	if (!event) return PrynResultNullArgument;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventDestroy, (PrynGuiEvent *event))
{
	PrynState *state;

	if (!event || !event->pControl) return PrynResultNullArgument;
	state = event->pControl->pState;
	prynGuiEventEmpty (event);
	prynFree (state, event);
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventClear, (PrynGuiEvent *event, PrynGuiEventType type, PrynGuiControl *control))
{
	if (!control || !event) return PrynResultNullArgument;
	prynClearObject (event);
	event->pType = type;
	event->pControl = control;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventCreate, (PrynGuiEvent **output, PrynGuiEventType type, PrynGuiControl *control))
{
	PrynGuiEvent *event;
	PrynResult result;

	if (output) *output = 0;
	if (!control || !output) return PrynResultNullArgument;
	prynAllocateClearedObject (control->pState, &event);
	if (!event) return PrynResultOutOfMemory;
	result = prynGuiEventClear (event, type, control);
	if (result >= 0 && output) *output = event;
	return result;
}

PrynGuiExport (PrynResult, prynGuiEventSetPainter, (PrynGuiEvent *event, PrynGuiPainter *painter))
{
	if (!event || !painter) return PrynResultNullArgument;
	if (event->pType != PrynGuiEventTypePaint) return PrynResultBadArgument;
	event->pPainter = painter;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventMousePosition, (PrynGuiEvent *event, int *x, int *y))
{
	if (!event || !x || !y) return PrynResultNullArgument;
	if (!PrynIsTrue (prynGuiEventTypeMatch (event->pType, PrynGuiEventTypeMouse))) return PrynResultBadArgument;
	*x = event->pMouseX;
	*y = event->pMouseY;
	return PrynResultSuccess;
}

PrynGuiExport (PrynResult, prynGuiEventMouseLeftButton, (PrynGuiEvent *event)) { return event && PrynIsTrue (prynGuiEventTypeMatch (event->pType, PrynGuiEventTypeMouse)) && event->pMouseLeftButton ? PrynResultTrue : PrynResultFalse; }
PrynGuiExport (PrynResult, prynGuiEventMouseMiddleButton, (PrynGuiEvent *event)) { return event && PrynIsTrue (prynGuiEventTypeMatch (event->pType, PrynGuiEventTypeMouse)) && event->pMouseMiddleButton ? PrynResultTrue : PrynResultFalse; }
PrynGuiExport (PrynResult, prynGuiEventMouseRightButton, (PrynGuiEvent *event)) { return event && PrynIsTrue (prynGuiEventTypeMatch (event->pType, PrynGuiEventTypeMouse)) && event->pMouseRightButton ? PrynResultTrue : PrynResultFalse; }
